#include <jni.h>
#include <string>
#include <functional>
#include <utils/file.h>
#include <leb128.h>
#include <cinttypes>
#include <code_item_accessors.h>
#include <android-base/stringprintf.h>
#include <dex_file_exception_helpers.h>
#include "android-base/strings.h"
#include "dex/dex_file.h"
#include "dex/dex_file_loader.h"
#include "dex/dex_instruction-inl.h"
#include "dex/code_item_accessors.h"

#include "dex/code_item_accessors-inl.h"
#include "dex/dex_file-inl.h"
#include "dex/dex_file_exception_helpers.h"
#include "dex/dex_file_loader.h"
#include "dex/dex_file_types.h"
#include "dex/dex_instruction-inl.h"

#include <android/log.h>
#include <unordered_map>


#define  LOG_TAG    "DUMP"
// #define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
// #define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)


#define snappendf(__buf, ...) snprintf(__buf + strlen(__buf), sizeof(__buf), __VA_ARGS__)


using namespace art_lkchan;
using namespace android_lkchan;

typedef uint8_t u1;
typedef uint16_t u2;
typedef uint32_t u4;
typedef uint64_t u8;
typedef int8_t s1;
typedef int16_t s2;
typedef int32_t s4;
typedef int64_t s8;

FILE *gOutFile = stdout;


/*
 * Gets 2 little-endian bytes.
 */
static inline u2 get2LE(unsigned char const *pSrc) {
    return pSrc[0] | (pSrc[1] << 8);
}

/*
 * Copies character data from "data" to "out", converting non-ASCII values
 * to fprintf format chars or an ASCII filler ('.' or '?').
 *
 * The output buffer must be able to hold (2*len)+1 bytes.  The result is
 * NULL-terminated.
 */
static void asciify(char *out, const unsigned char *data, size_t len) {
    while (len--) {
        if (*data < 0x20) {
            // Could do more here, but we don't need them yet.
            switch (*data) {
                case '\0':
                    *out++ = '\\';
                    *out++ = '0';
                    break;
                case '\n':
                    *out++ = '\\';
                    *out++ = 'n';
                    break;
                default:
                    *out++ = '.';
                    break;
            }  // switch
        } else if (*data >= 0x80) {
            *out++ = '?';
        } else {
            *out++ = *data;
        }
        data++;
    }  // while
    *out = '\0';
}

/*
 * Dumps the file header.
 */
static void dumpFileHeader(const DexFile *pDexFile) {
    const DexFile::Header &pHeader = pDexFile->GetHeader();
    char sanitized[sizeof(pHeader.magic_) * 2 + 1];
    fprintf(gOutFile, "DEX file header:\n");
    asciify(sanitized, pHeader.magic_, sizeof(pHeader.magic_));
    fprintf(gOutFile, "magic               : '%s'\n", sanitized);
    fprintf(gOutFile, "checksum            : %08x\n", pHeader.checksum_);
    fprintf(gOutFile, "signature           : %02x%02x...%02x%02x\n",
            pHeader.signature_[0], pHeader.signature_[1],
            pHeader.signature_[DexFile::kSha1DigestSize - 2],
            pHeader.signature_[DexFile::kSha1DigestSize - 1]);
    fprintf(gOutFile, "file_size           : %d\n", pHeader.file_size_);
    fprintf(gOutFile, "header_size         : %d\n", pHeader.header_size_);
    fprintf(gOutFile, "link_size           : %d\n", pHeader.link_size_);
    fprintf(gOutFile, "link_off            : %d (0x%06x)\n",
            pHeader.link_off_, pHeader.link_off_);
    fprintf(gOutFile, "string_ids_size     : %d\n", pHeader.string_ids_size_);
    fprintf(gOutFile, "string_ids_off      : %d (0x%06x)\n",
            pHeader.string_ids_off_, pHeader.string_ids_off_);
    fprintf(gOutFile, "type_ids_size       : %d\n", pHeader.type_ids_size_);
    fprintf(gOutFile, "type_ids_off        : %d (0x%06x)\n",
            pHeader.type_ids_off_, pHeader.type_ids_off_);
    fprintf(gOutFile, "proto_ids_size      : %d\n", pHeader.proto_ids_size_);
    fprintf(gOutFile, "proto_ids_off       : %d (0x%06x)\n",
            pHeader.proto_ids_off_, pHeader.proto_ids_off_);
    fprintf(gOutFile, "field_ids_size      : %d\n", pHeader.field_ids_size_);
    fprintf(gOutFile, "field_ids_off       : %d (0x%06x)\n",
            pHeader.field_ids_off_, pHeader.field_ids_off_);
    fprintf(gOutFile, "method_ids_size     : %d\n", pHeader.method_ids_size_);
    fprintf(gOutFile, "method_ids_off      : %d (0x%06x)\n",
            pHeader.method_ids_off_, pHeader.method_ids_off_);
    fprintf(gOutFile, "class_defs_size     : %d\n", pHeader.class_defs_size_);
    fprintf(gOutFile, "class_defs_off      : %d (0x%06x)\n",
            pHeader.class_defs_off_, pHeader.class_defs_off_);
    fprintf(gOutFile, "data_size           : %d\n", pHeader.data_size_);
    fprintf(gOutFile, "data_off            : %d (0x%06x)\n\n",
            pHeader.data_off_, pHeader.data_off_);
}


/*
 * Counts the number of '1' bits in a word.
 */
static int countOnes(u4 val) {
    val = val - ((val >> 1) & 0x55555555);
    val = (val & 0x33333333) + ((val >> 2) & 0x33333333);
    return (((val + (val >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}

/*
 * Flags for use with createAccessFlagStr().
 */
enum AccessFor {
    kAccessForClass = 0, kAccessForMethod = 1, kAccessForField = 2, kAccessForMAX
};

const int kNumFlags = 18;
static const int kMaxClassCount = 65535;

/*
 * Creates a new string with human-readable access flags.
 *
 * In the base language the access_flags fields are type u2; in Dalvik
 * they're u4.
 */
static char *createAccessFlagStr(u4 flags, AccessFor forWhat) {
    static const char *kAccessStrings[kAccessForMAX][kNumFlags] = {
            {
                    "PUBLIC",                /* 0x00001 */
                    "PRIVATE",               /* 0x00002 */
                    "PROTECTED",             /* 0x00004 */
                    "STATIC",                /* 0x00008 */
                    "FINAL",                 /* 0x00010 */
                    "?",                     /* 0x00020 */
                    "?",                     /* 0x00040 */
                    "?",                     /* 0x00080 */
                    "?",                     /* 0x00100 */
                    "INTERFACE",             /* 0x00200 */
                    "ABSTRACT",              /* 0x00400 */
                    "?",                     /* 0x00800 */
                    "SYNTHETIC",             /* 0x01000 */
                    "ANNOTATION",            /* 0x02000 */
                    "ENUM",                  /* 0x04000 */
                    "?",                     /* 0x08000 */
                    "VERIFIED",              /* 0x10000 */
                    "OPTIMIZED",             /* 0x20000 */
            },
            {
                    "PUBLIC",                /* 0x00001 */
                    "PRIVATE",               /* 0x00002 */
                    "PROTECTED",             /* 0x00004 */
                    "STATIC",                /* 0x00008 */
                    "FINAL",                 /* 0x00010 */
                    "SYNCHRONIZED",          /* 0x00020 */
                    "BRIDGE",                /* 0x00040 */
                    "VARARGS",               /* 0x00080 */
                    "NATIVE",                /* 0x00100 */
                    "?",                     /* 0x00200 */
                    "ABSTRACT",              /* 0x00400 */
                    "STRICT",                /* 0x00800 */
                    "SYNTHETIC",             /* 0x01000 */
                    "?",                     /* 0x02000 */
                    "?",                     /* 0x04000 */
                    "MIRANDA",               /* 0x08000 */
                    "CONSTRUCTOR",           /* 0x10000 */
                    "DECLARED_SYNCHRONIZED", /* 0x20000 */
            },
            {
                    "PUBLIC",                /* 0x00001 */
                    "PRIVATE",               /* 0x00002 */
                    "PROTECTED",             /* 0x00004 */
                    "STATIC",                /* 0x00008 */
                    "FINAL",                 /* 0x00010 */
                    "?",                     /* 0x00020 */
                    "VOLATILE",              /* 0x00040 */
                    "TRANSIENT",             /* 0x00080 */
                    "?",                     /* 0x00100 */
                    "?",                     /* 0x00200 */
                    "?",                     /* 0x00400 */
                    "?",                     /* 0x00800 */
                    "SYNTHETIC",             /* 0x01000 */
                    "?",                     /* 0x02000 */
                    "ENUM",                  /* 0x04000 */
                    "?",                     /* 0x08000 */
                    "?",                     /* 0x10000 */
                    "?",                     /* 0x20000 */
            },
    };

    // Allocate enough storage to hold the expected number of strings,
    // plus a space between each.  We over-allocate, using the longest
    // string above as the base metric.
    const int kLongest = 21;  // The strlen of longest string above.
    const int count = countOnes(flags);
    char *str;
    char *cp;
    cp = str = reinterpret_cast<char *>(malloc(count * (kLongest + 1) + 1));

    for (int i = 0; i < kNumFlags; i++) {
        if (flags & 0x01) {
            const char *accessStr = kAccessStrings[forWhat][i];
            const int len = strlen(accessStr);
            if (cp != str) {
                *cp++ = ' ';
            }
            memcpy(cp, accessStr, len);
            cp += len;
        }
        flags >>= 1;
    }  // for

    *cp = '\0';
    return str;
}

/*
 * Returns string representing the boolean value.
 */
static const char *strBool(bool val) {
    return val ? "true" : "false";
}

/*
 * Reads variable width value, possibly sign extended at the last defined byte.
 */
static u8 readVarWidth(const u1 **data, u1 arg, bool sign_extend) {
    u8 value = 0;
    for (u4 i = 0; i <= arg; i++) {
        value |= static_cast<u8>(*(*data)++) << (i * 8);
    }
    if (sign_extend) {
        int shift = (7 - arg) * 8;
        return (static_cast<s8>(value) << shift) >> shift;
    }
    return value;
}

/*
 * Dumps a string value with some escape characters.
 */
static void dumpEscapedString(const char *p) {
    fputs("\"", gOutFile);
    for (; *p; p++) {
        switch (*p) {
            case '\\':
                fputs("\\\\", gOutFile);
                break;
            case '\"':
                fputs("\\\"", gOutFile);
                break;
            case '\t':
                fputs("\\t", gOutFile);
                break;
            case '\n':
                fputs("\\n", gOutFile);
                break;
            case '\r':
                fputs("\\r", gOutFile);
                break;
            default:
                putc(*p, gOutFile);
        }  // switch
    }  // for
    fputs("\"", gOutFile);
}


/*
 * Dumps encoded value.
 */
static void dumpEncodedValue(const DexFile *pDexFile, const u1 **data);  // forward

static void dumpEncodedValue(const DexFile *pDexFile, const u1 **data, u1 type, u1 arg) {
    switch (type) {
        case DexFile::kDexAnnotationByte:
            fprintf(gOutFile, "%" PRId8, static_cast<s1>(readVarWidth(data, arg, false)));
            break;
        case DexFile::kDexAnnotationShort:
            fprintf(gOutFile, "%" PRId16, static_cast<s2>(readVarWidth(data, arg, true)));
            break;
        case DexFile::kDexAnnotationChar:
            fprintf(gOutFile, "%" PRIu16, static_cast<u2>(readVarWidth(data, arg, false)));
            break;
        case DexFile::kDexAnnotationInt:
            fprintf(gOutFile, "%" PRId32, static_cast<s4>(readVarWidth(data, arg, true)));
            break;
        case DexFile::kDexAnnotationLong:
            fprintf(gOutFile, "%" PRId64, static_cast<s8>(readVarWidth(data, arg, true)));
            break;
        case DexFile::kDexAnnotationFloat: {
            // Fill on right.
            union {
                float f;
                u4 data;
            } conv;
            conv.data = static_cast<u4>(readVarWidth(data, arg, false)) << (3 - arg) * 8;
            fprintf(gOutFile, "%g", conv.f);
            break;
        }
        case DexFile::kDexAnnotationDouble: {
            // Fill on right.
            union {
                double d;
                u8 data;
            } conv;
            conv.data = readVarWidth(data, arg, false) << (7 - arg) * 8;
            fprintf(gOutFile, "%g", conv.d);
            break;
        }
        case DexFile::kDexAnnotationString: {
            const u4 idx = static_cast<u4>(readVarWidth(data, arg, false));
            if (true) {
                dumpEscapedString(pDexFile->StringDataByIdx(dex::StringIndex(idx)));
            } else {
//                dumpXmlAttribute(pDexFile->StringDataByIdx(dex::StringIndex(idx)));
            }
            break;
        }
        case DexFile::kDexAnnotationType: {
            const u4 str_idx = static_cast<u4>(readVarWidth(data, arg, false));
            fputs(pDexFile->StringByTypeIdx(dex::TypeIndex(str_idx)), gOutFile);
            break;
        }
        case DexFile::kDexAnnotationField:
        case DexFile::kDexAnnotationEnum: {
            const u4 field_idx = static_cast<u4>(readVarWidth(data, arg, false));
            const DexFile::FieldId &pFieldId = pDexFile->GetFieldId(field_idx);
            fputs(pDexFile->StringDataByIdx(pFieldId.name_idx_), gOutFile);
            break;
        }
        case DexFile::kDexAnnotationMethod: {
            const u4 method_idx = static_cast<u4>(readVarWidth(data, arg, false));
            const DexFile::MethodId &pMethodId = pDexFile->GetMethodId(method_idx);
            fputs(pDexFile->StringDataByIdx(pMethodId.name_idx_), gOutFile);
            break;
        }
        case DexFile::kDexAnnotationArray: {
            fputc('{', gOutFile);
            // Decode and display all elements.
            const u4 size = DecodeUnsignedLeb128(data);
            for (u4 i = 0; i < size; i++) {
                fputc(' ', gOutFile);
                dumpEncodedValue(pDexFile, data);
            }
            fputs(" }", gOutFile);
            break;
        }
        case DexFile::kDexAnnotationAnnotation: {
            const u4 type_idx = DecodeUnsignedLeb128(data);
            fputs(pDexFile->StringByTypeIdx(dex::TypeIndex(type_idx)), gOutFile);
            // Decode and display all name=value pairs.
            const u4 size = DecodeUnsignedLeb128(data);
            for (u4 i = 0; i < size; i++) {
                const u4 name_idx = DecodeUnsignedLeb128(data);
                fputc(' ', gOutFile);
                fputs(pDexFile->StringDataByIdx(dex::StringIndex(name_idx)), gOutFile);
                fputc('=', gOutFile);
                dumpEncodedValue(pDexFile, data);
            }
            break;
        }
        case DexFile::kDexAnnotationNull:
            fputs("null", gOutFile);
            break;
        case DexFile::kDexAnnotationBoolean:
            fputs(strBool(arg), gOutFile);
            break;
        default:
            fputs("????", gOutFile);
            break;
    }  // switch
}

/*
 * Dumps encoded value with prefix.
 */
static void dumpEncodedValue(const DexFile *pDexFile, const u1 **data) {
    const u1 enc = *(*data)++;
    dumpEncodedValue(pDexFile, data, enc & 0x1f, enc >> 5);
}


/*
 * Dumps a static (class) field.
 */
static void dumpSField(const DexFile *pDexFile, u4 idx, u4 flags, int i, const u1 **data) {
//    // Bail for anything private if export only requested.
//    if (gOptions.exportsOnly && (flags & (kAccPublic | kAccProtected)) == 0) {
//        return;
//    }

    const DexFile::FieldId &pFieldId = pDexFile->GetFieldId(idx);
    const char *name = pDexFile->StringDataByIdx(pFieldId.name_idx_);
    const char *typeDescriptor = pDexFile->StringByTypeIdx(pFieldId.type_idx_);
    const char *backDescriptor = pDexFile->StringByTypeIdx(pFieldId.class_idx_);
    char *accessStr = createAccessFlagStr(flags, kAccessForField);

    if (true) {
        fprintf(gOutFile, "    #%d              : (in %s)\n", i, backDescriptor);
        fprintf(gOutFile, "      name          : '%s'\n", name);
        fprintf(gOutFile, "      type          : '%s'\n", typeDescriptor);
        fprintf(gOutFile, "      access        : 0x%04x (%s)\n", flags, accessStr);
        if (data != nullptr) {
            fputs("      value         : ", gOutFile);
            dumpEncodedValue(pDexFile, data);
            fputs("\n", gOutFile);
        }
    } else {
    }

    free(accessStr);
}


/*
 * Converts a single-character primitive type into human-readable form.
 */
static const char *primitiveTypeLabel(char typeChar) {
    switch (typeChar) {
        case 'B':
            return "byte";
        case 'C':
            return "char";
        case 'D':
            return "double";
        case 'F':
            return "float";
        case 'I':
            return "int";
        case 'J':
            return "long";
        case 'S':
            return "short";
        case 'V':
            return "void";
        case 'Z':
            return "boolean";
        default:
            return "UNKNOWN";
    }  // switch
}


/*
 * Converts a type descriptor to human-readable "dotted" form.  For
 * example, "Ljava/lang/String;" becomes "java.lang.String", and
 * "[I" becomes "int[]".  Also converts '$' to '.', which means this
 * form can't be converted back to a descriptor.
 */
static std::unique_ptr<char[]> descriptorToDot(const char *str) {
    int targetLen = strlen(str);
    int offset = 0;

    // Strip leading [s; will be added to end.
    while (targetLen > 1 && str[offset] == '[') {
        offset++;
        targetLen--;
    }  // while

    const int arrayDepth = offset;

    if (targetLen == 1) {
        // Primitive type.
        str = primitiveTypeLabel(str[offset]);
        offset = 0;
        targetLen = strlen(str);
    } else {
        // Account for leading 'L' and trailing ';'.
        if (targetLen >= 2 && str[offset] == 'L' &&
            str[offset + targetLen - 1] == ';') {
            targetLen -= 2;
            offset++;
        }
    }

    // Copy class name over.
    std::unique_ptr<char[]> newStr(new char[targetLen + arrayDepth * 2 + 1]);
    int i = 0;
    for (; i < targetLen; i++) {
        const char ch = str[offset + i];
        newStr[i] = (ch == '/' || ch == '$') ? '.' : ch;
    }  // for

    // Add the appropriate number of brackets for arrays.
    for (int j = 0; j < arrayDepth; j++) {
        newStr[i++] = '[';
        newStr[i++] = ']';
    }  // for

    newStr[i] = '\0';
    return newStr;
}

/*
 * Callback for dumping each positions table entry.
 */
static bool dumpPositionsCb(void * /*context*/, const DexFile::PositionInfo &entry) {
    fprintf(gOutFile, "        0x%04x line=%d\n", entry.address_, entry.line_);
    return false;
}

/*
 * Callback for dumping locals table entry.
 */
static void dumpLocalsCb(void * /*context*/, const DexFile::LocalInfo &entry) {
    const char *signature = entry.signature_ != nullptr ? entry.signature_ : "";
    fprintf(gOutFile, "        0x%04x - 0x%04x reg=%d %s %s %s\n",
            entry.start_address_, entry.end_address_, entry.reg_,
            entry.name_, entry.descriptor_, signature);
}

/*
 * Helper for dumpInstruction(), which builds the string
 * representation for the index in the given instruction.
 * Returns a pointer to a buffer of sufficient size.
 */
static std::unique_ptr<char[]> indexString(const DexFile *pDexFile,
                                           const Instruction *pDecInsn,
                                           size_t bufSize) {
    static const u4 kInvalidIndex = std::numeric_limits<u4>::max();
    std::unique_ptr<char[]> buf(new char[bufSize]);
    // Determine index and width of the string.
    u4 index = 0;
    u4 secondary_index = kInvalidIndex;
    u4 width = 4;
    switch (Instruction::FormatOf(pDecInsn->Opcode())) {
        // SOME NOT SUPPORTED:
        // case Instruction::k20bc:
        case Instruction::k21c:
        case Instruction::k35c:
            // case Instruction::k35ms:
        case Instruction::k3rc:
            // case Instruction::k3rms:
            // case Instruction::k35mi:
            // case Instruction::k3rmi:
            index = pDecInsn->VRegB();
            width = 4;
            break;
        case Instruction::k31c:
            index = pDecInsn->VRegB();
            width = 8;
            break;
        case Instruction::k22c:
            // case Instruction::k22cs:
            index = pDecInsn->VRegC();
            width = 4;
            break;
        case Instruction::k45cc:
        case Instruction::k4rcc:
            index = pDecInsn->VRegB();
            secondary_index = pDecInsn->VRegH();
            width = 4;
            break;
        default:
            break;
    }  // switch

    // Determine index type.
    size_t outSize = 0;
    switch (Instruction::IndexTypeOf(pDecInsn->Opcode())) {
        case Instruction::kIndexUnknown:
            // This function should never get called for this type, but do
            // something sensible here, just to help with debugging.
            outSize = snprintf(buf.get(), bufSize, "<unknown-index>");
            break;
        case Instruction::kIndexNone:
            // This function should never get called for this type, but do
            // something sensible here, just to help with debugging.
            outSize = snprintf(buf.get(), bufSize, "<no-index>");
            break;
        case Instruction::kIndexTypeRef:
            if (index < pDexFile->GetHeader().type_ids_size_) {
                const char *tp = pDexFile->StringByTypeIdx(dex::TypeIndex(index));
                outSize = snprintf(buf.get(), bufSize, "%s // type@%0*x", tp, width, index);
            } else {
                outSize = snprintf(buf.get(), bufSize, "<type?> // type@%0*x", width, index);
            }
            break;
        case Instruction::kIndexStringRef:
            if (index < pDexFile->GetHeader().string_ids_size_) {
                const char *st = pDexFile->StringDataByIdx(dex::StringIndex(index));
                outSize = snprintf(buf.get(), bufSize, "\"%s\" // string@%0*x", st, width, index);
            } else {
                outSize = snprintf(buf.get(), bufSize, "<string?> // string@%0*x", width, index);
            }
            break;
        case Instruction::kIndexMethodRef:
            if (index < pDexFile->GetHeader().method_ids_size_) {
                const DexFile::MethodId &pMethodId = pDexFile->GetMethodId(index);
                const char *name = pDexFile->StringDataByIdx(pMethodId.name_idx_);
                const Signature signature = pDexFile->GetMethodSignature(pMethodId);
                const char *backDescriptor = pDexFile->StringByTypeIdx(pMethodId.class_idx_);
                outSize = snprintf(buf.get(), bufSize, "%s.%s:%s // method@%0*x",
                                   backDescriptor, name, signature.ToString().c_str(), width,
                                   index);
            } else {
                outSize = snprintf(buf.get(), bufSize, "<method?> // method@%0*x", width, index);
            }
            break;
        case Instruction::kIndexFieldRef:
            if (index < pDexFile->GetHeader().field_ids_size_) {
                const DexFile::FieldId &pFieldId = pDexFile->GetFieldId(index);
                const char *name = pDexFile->StringDataByIdx(pFieldId.name_idx_);
                const char *typeDescriptor = pDexFile->StringByTypeIdx(pFieldId.type_idx_);
                const char *backDescriptor = pDexFile->StringByTypeIdx(pFieldId.class_idx_);
                outSize = snprintf(buf.get(), bufSize, "%s.%s:%s // field@%0*x",
                                   backDescriptor, name, typeDescriptor, width, index);
            } else {
                outSize = snprintf(buf.get(), bufSize, "<field?> // field@%0*x", width, index);
            }
            break;
        case Instruction::kIndexVtableOffset:
            outSize = snprintf(buf.get(), bufSize, "[%0*x] // vtable #%0*x",
                               width, index, width, index);
            break;
        case Instruction::kIndexFieldOffset:
            outSize = snprintf(buf.get(), bufSize, "[obj+%0*x]", width, index);
            break;
        case Instruction::kIndexMethodAndProtoRef: {
            std::string method("<method?>");
            std::string proto("<proto?>");
            if (index < pDexFile->GetHeader().method_ids_size_) {
                const DexFile::MethodId &pMethodId = pDexFile->GetMethodId(index);
                const char *name = pDexFile->StringDataByIdx(pMethodId.name_idx_);
                const Signature signature = pDexFile->GetMethodSignature(pMethodId);
                const char *backDescriptor = pDexFile->StringByTypeIdx(pMethodId.class_idx_);
                method = android_lkchan::base::StringPrintf("%s.%s:%s",
                                                            backDescriptor,
                                                            name,
                                                            signature.ToString().c_str());
            }
            if (secondary_index < pDexFile->GetHeader().proto_ids_size_) {
                const DexFile::ProtoId &protoId = pDexFile->GetProtoId(secondary_index);
                const Signature signature = pDexFile->GetProtoSignature(protoId);
                proto = signature.ToString();
            }
            outSize = snprintf(buf.get(), bufSize, "%s, %s // method@%0*x, proto@%0*x",
                               method.c_str(), proto.c_str(), width, index, width, secondary_index);
            break;
        }
        case Instruction::kIndexCallSiteRef:
            // Call site information is too large to detail in disassembly so just output the index.
            outSize = snprintf(buf.get(), bufSize, "call_site@%0*x", width, index);
            break;
        case Instruction::kIndexMethodHandleRef:
            // Method handle information is too large to detail in disassembly so just output the index.
            outSize = snprintf(buf.get(), bufSize, "method_handle@%0*x", width, index);
            break;
        case Instruction::kIndexProtoRef:
            if (index < pDexFile->GetHeader().proto_ids_size_) {
                const DexFile::ProtoId &protoId = pDexFile->GetProtoId(index);
                const Signature signature = pDexFile->GetProtoSignature(protoId);
                const std::string &proto = signature.ToString();
                outSize = snprintf(buf.get(), bufSize, "%s // proto@%0*x", proto.c_str(), width,
                                   index);
            } else {
                outSize = snprintf(buf.get(), bufSize, "<?> // proto@%0*x", width, index);
            }
            break;
    }  // switch

    if (outSize == 0) {
        // The index type has not been handled in the switch above.
        outSize = snprintf(buf.get(), bufSize, "<?>");
    }

    // Determine success of string construction.
    if (outSize >= bufSize) {
        // The buffer wasn't big enough; retry with computed size. Note: snprintf()
        // doesn't count/ the '\0' as part of its returned size, so we add explicit
        // space for it here.
        return indexString(pDexFile, pDecInsn, outSize + 1);
    }
    return buf;
}


/*
 * Dumps a single instruction.
 */
static void dumpInstruction(const DexFile *pDexFile,
                            const DexFile::CodeItem *pCode,
                            u4 codeOffset, u4 insnIdx, u4 insnWidth,
                            const Instruction *pDecInsn) {
    // Address of instruction (expressed as byte offset).
    char buf[1024] = "";
    snprintf(buf, sizeof(buf), "%06x:", codeOffset + 0x10 + insnIdx * 2);

    // Dump (part of) raw bytes.
    CodeItemInstructionAccessor accessor(*pDexFile, pCode);
    for (u4 i = 0; i < 8; i++) {
        if (i < insnWidth) {
            if (i == 7) {
                snprintf(buf + strlen(buf), sizeof(buf), " ... ");
            } else {
                // Print 16-bit value in little-endian order.
                const u1 *bytePtr = (const u1 *) &accessor.Insns()[insnIdx + i];
                snprintf(buf + strlen(buf), sizeof(buf), " %02x%02x", bytePtr[0], bytePtr[1]);
            }
        } else {
            // eggfly
            snprintf(buf + strlen(buf), sizeof(buf), "     ");
        }
    }  // for

    // Dump pseudo-instruction or opcode.
    if (pDecInsn->Opcode() == Instruction::NOP) {
        const u2 instr = get2LE((const u1 *) &accessor.Insns()[insnIdx]);
        if (instr == Instruction::kPackedSwitchSignature) {
            snprintf(buf + strlen(buf), sizeof(buf), "|%04x: packed-switch-data (%d units)",
                     insnIdx, insnWidth);
        } else if (instr == Instruction::kSparseSwitchSignature) {
            snprintf(buf + strlen(buf), sizeof(buf), "|%04x: sparse-switch-data (%d units)",
                     insnIdx, insnWidth);
        } else if (instr == Instruction::kArrayDataSignature) {
            snprintf(buf + strlen(buf), sizeof(buf), "|%04x: array-data (%d units)", insnIdx,
                     insnWidth);
        } else {
            snprintf(buf + strlen(buf), sizeof(buf), "|%04x: nop // spacer", insnIdx);
        }
    } else {
        snprintf(buf + strlen(buf), sizeof(buf), "|%04x: %s", insnIdx, pDecInsn->Name());
    }

    // Set up additional argument.
    std::unique_ptr<char[]> indexBuf;
    if (Instruction::IndexTypeOf(pDecInsn->Opcode()) != Instruction::kIndexNone) {
        indexBuf = indexString(pDexFile, pDecInsn, 200);
    }

    // Dump the instruction.
    //
    // NOTE: pDecInsn->DumpString(pDexFile) differs too much from original.
    //
    switch (Instruction::FormatOf(pDecInsn->Opcode())) {
        case Instruction::k10x:        // op
            break;
        case Instruction::k12x:        // op vA, vB
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, v%d", pDecInsn->VRegA(),
                     pDecInsn->VRegB());
            break;
        case Instruction::k11n:        // op vA, #+B
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, #int %d // #%x",
                     pDecInsn->VRegA(), (s4) pDecInsn->VRegB(), (u1) pDecInsn->VRegB());
            break;
        case Instruction::k11x:        // op vAA
            snprintf(buf + strlen(buf), sizeof(buf), " v%d", pDecInsn->VRegA());
            break;
        case Instruction::k10t:        // op +AA
        case Instruction::k20t: {      // op +AAAA
            const s4 targ = (s4) pDecInsn->VRegA();
            snprintf(buf + strlen(buf), sizeof(buf), " %04x // %c%04x",
                     insnIdx + targ,
                     (targ < 0) ? '-' : '+',
                     (targ < 0) ? -targ : targ);
            break;
        }
        case Instruction::k22x:        // op vAA, vBBBB
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, v%d", pDecInsn->VRegA(),
                     pDecInsn->VRegB());
            break;
        case Instruction::k21t: {     // op vAA, +BBBB
            const s4 targ = (s4) pDecInsn->VRegB();
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, %04x // %c%04x", pDecInsn->VRegA(),
                     insnIdx + targ,
                     (targ < 0) ? '-' : '+',
                     (targ < 0) ? -targ : targ);
            break;
        }
        case Instruction::k21s:        // op vAA, #+BBBB
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, #int %d // #%x",
                     pDecInsn->VRegA(), (s4) pDecInsn->VRegB(), (u2) pDecInsn->VRegB());
            break;
        case Instruction::k21h:        // op vAA, #+BBBB0000[00000000]
            // The printed format varies a bit based on the actual opcode.
            if (pDecInsn->Opcode() == Instruction::CONST_HIGH16) {
                const s4 value = pDecInsn->VRegB() << 16;
                snprintf(buf + strlen(buf), sizeof(buf), " v%d, #int %d // #%x",
                         pDecInsn->VRegA(), value, (u2) pDecInsn->VRegB());
            } else {
                const s8 value = ((s8) pDecInsn->VRegB()) << 48;
                snprintf(buf + strlen(buf), sizeof(buf), " v%d, #long %" PRId64 " // #%x",
                         pDecInsn->VRegA(), value, (u2) pDecInsn->VRegB());
            }
            break;
        case Instruction::k21c:        // op vAA, thing@BBBB
        case Instruction::k31c:        // op vAA, thing@BBBBBBBB
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, %s", pDecInsn->VRegA(), indexBuf.get());
            break;
        case Instruction::k23x:        // op vAA, vBB, vCC
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, v%d, v%d",
                     pDecInsn->VRegA(), pDecInsn->VRegB(), pDecInsn->VRegC());
            break;
        case Instruction::k22b:        // op vAA, vBB, #+CC
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, v%d, #int %d // #%02x",
                     pDecInsn->VRegA(), pDecInsn->VRegB(),
                     (s4) pDecInsn->VRegC(), (u1) pDecInsn->VRegC());
            break;
        case Instruction::k22t: {      // op vA, vB, +CCCC
            const s4 targ = (s4) pDecInsn->VRegC();
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, v%d, %04x // %c%04x",
                     pDecInsn->VRegA(), pDecInsn->VRegB(),
                     insnIdx + targ,
                     (targ < 0) ? '-' : '+',
                     (targ < 0) ? -targ : targ);
            break;
        }
        case Instruction::k22s:        // op vA, vB, #+CCCC
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, v%d, #int %d // #%04x",
                     pDecInsn->VRegA(), pDecInsn->VRegB(),
                     (s4) pDecInsn->VRegC(), (u2) pDecInsn->VRegC());
            break;
        case Instruction::k22c:        // op vA, vB, thing@CCCC
            // NOT SUPPORTED:
            // case Instruction::k22cs:    // [opt] op vA, vB, field offset CCCC
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, v%d, %s",
                     pDecInsn->VRegA(), pDecInsn->VRegB(), indexBuf.get());
            break;
        case Instruction::k30t:
            snprintf(buf + strlen(buf), sizeof(buf), " #%08x", pDecInsn->VRegA());
            break;
        case Instruction::k31i: {     // op vAA, #+BBBBBBBB
            // This is often, but not always, a float.
            union {
                float f;
                u4 i;
            } conv;
            conv.i = pDecInsn->VRegB();
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, #float %g // #%08x",
                     pDecInsn->VRegA(), conv.f, pDecInsn->VRegB());
            break;
        }
        case Instruction::k31t:       // op vAA, offset +BBBBBBBB
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, %08x // +%08x",
                     pDecInsn->VRegA(), insnIdx + pDecInsn->VRegB(), pDecInsn->VRegB());
            break;
        case Instruction::k32x:        // op vAAAA, vBBBB
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, v%d", pDecInsn->VRegA(),
                     pDecInsn->VRegB());
            break;
        case Instruction::k35c:       // op {vC, vD, vE, vF, vG}, thing@BBBB
        case Instruction::k45cc: {    // op {vC, vD, vE, vF, vG}, method@BBBB, proto@HHHH
            // NOT SUPPORTED:
            // case Instruction::k35ms:       // [opt] invoke-virtual+super
            // case Instruction::k35mi:       // [opt] inline invoke
            u4 arg[Instruction::kMaxVarArgRegs];
            pDecInsn->GetVarArgs(arg);
            snprintf(buf + strlen(buf), sizeof(buf), " {");
            for (int i = 0, n = pDecInsn->VRegA(); i < n; i++) {
                if (i == 0) {
                    snprintf(buf + strlen(buf), sizeof(buf), "v%d", arg[i]);
                } else {
                    snprintf(buf + strlen(buf), sizeof(buf), ", v%d", arg[i]);
                }
            }  // for
            snprintf(buf + strlen(buf), sizeof(buf), "}, %s", indexBuf.get());
            break;
        }
        case Instruction::k3rc:        // op {vCCCC .. v(CCCC+AA-1)}, thing@BBBB
        case Instruction::k4rcc: {     // op {vCCCC .. v(CCCC+AA-1)}, method@BBBB, proto@HHHH
            // NOT SUPPORTED:
            // case Instruction::k3rms:       // [opt] invoke-virtual+super/range
            // case Instruction::k3rmi:       // [opt] execute-inline/range
            // This doesn't match the "dx" output when some of the args are
            // 64-bit values -- dx only shows the first register.
            snprintf(buf + strlen(buf), sizeof(buf), " {");
            for (int i = 0, n = pDecInsn->VRegA(); i < n; i++) {
                if (i == 0) {
                    snprintf(buf + strlen(buf), sizeof(buf), "v%d", pDecInsn->VRegC() + i);
                } else {
                    snprintf(buf + strlen(buf), sizeof(buf), ", v%d", pDecInsn->VRegC() + i);
                }
            }  // for
            snprintf(buf + strlen(buf), sizeof(buf), "}, %s", indexBuf.get());
        }
            break;
        case Instruction::k51l: {      // op vAA, #+BBBBBBBBBBBBBBBB
            // This is often, but not always, a double.
            union {
                double d;
                u8 j;
            } conv;
            conv.j = pDecInsn->WideVRegB();
            snprintf(buf + strlen(buf), sizeof(buf), " v%d, #double %g // #%016" PRIx64,
                     pDecInsn->VRegA(), conv.d, pDecInsn->WideVRegB());
            break;
        }
            // NOT SUPPORTED:
            // case Instruction::k00x:        // unknown op or breakpoint
            //    break;
        default:
            snprintf(buf + strlen(buf), sizeof(buf), " ???");
            break;
    }  // switch

    snprintf(buf + strlen(buf), sizeof(buf), "\n");

    // LOG(WARNING) << "DUMP: " << buf;
    LOGI("%s", buf);
}


/*
 * Dumps a bytecode disassembly.
 */
static void dumpBytecodes(const DexFile *pDexFile, u4 idx,
                          const DexFile::CodeItem *pCode, u4 codeOffset) {
    const DexFile::MethodId &pMethodId = pDexFile->GetMethodId(idx);
    const char *name = pDexFile->StringDataByIdx(pMethodId.name_idx_);
    const Signature signature = pDexFile->GetMethodSignature(pMethodId);
    const char *backDescriptor = pDexFile->StringByTypeIdx(pMethodId.class_idx_);

    // Generate header.
    std::unique_ptr<char[]> dot(descriptorToDot(backDescriptor));
    fprintf(gOutFile, "%06x:                                        |[%06x] %s.%s:%s\n",
            codeOffset, codeOffset, dot.get(), name, signature.ToString().c_str());

    // Iterate over all instructions.
    CodeItemDataAccessor accessor(*pDexFile, pCode);
    const u4 maxPc = accessor.InsnsSizeInCodeUnits();
    for (const DexInstructionPcPair &pair : accessor) {
        const u4 dexPc = pair.DexPc();
        if (dexPc >= maxPc) {
            LOG(WARNING) << "GLITCH: run-away instruction at idx=0x" << std::hex << dexPc;
            break;
        }
        const Instruction *instruction = &pair.Inst();
        const u4 insnWidth = instruction->SizeInCodeUnits();
        if (insnWidth == 0) {
            LOG(WARNING) << "GLITCH: zero-width instruction at idx=0x" << std::hex << dexPc;
            break;
        }
        dumpInstruction(pDexFile, pCode, codeOffset, dexPc, insnWidth, instruction);
    }  // for
}

/*
 * Dumps the catches table associated with the code.
 */
static void dumpCatches(const DexFile *pDexFile, const DexFile::CodeItem *pCode) {
    CodeItemDataAccessor accessor(*pDexFile, pCode);
    const u4 triesSize = accessor.TriesSize();

    // No catch table.
    if (triesSize == 0) {
        fprintf(gOutFile, "      catches       : (none)\n");
        return;
    }

    // Dump all table entries.
    fprintf(gOutFile, "      catches       : %d\n", triesSize);
    for (const DexFile::TryItem &try_item : accessor.TryItems()) {
        const u4 start = try_item.start_addr_;
        const u4 end = start + try_item.insn_count_;
        fprintf(gOutFile, "        0x%04x - 0x%04x\n", start, end);
        for (CatchHandlerIterator it(accessor, try_item); it.HasNext(); it.Next()) {
            const dex::TypeIndex tidx = it.GetHandlerTypeIndex();
            const char *descriptor = (!tidx.IsValid()) ? "<any>" : pDexFile->StringByTypeIdx(tidx);
            fprintf(gOutFile, "          %s -> 0x%04x\n", descriptor, it.GetHandlerAddress());
        }  // for
    }  // for
}

/*
 * Dumps code of a method.
 */
static void dumpCode(const DexFile *pDexFile, u4 idx, u4 flags,
                     const DexFile::CodeItem *pCode, u4 codeOffset) {
    CodeItemDebugInfoAccessor accessor(*pDexFile, pCode, idx);

    fprintf(gOutFile, "      registers     : %d\n", accessor.RegistersSize());
    fprintf(gOutFile, "      ins           : %d\n", accessor.InsSize());
    fprintf(gOutFile, "      outs          : %d\n", accessor.OutsSize());
    fprintf(gOutFile, "      insns size    : %d 16-bit code units\n",
            accessor.InsnsSizeInCodeUnits());

    // Bytecode disassembly, if requested.
    if (true) {
        dumpBytecodes(pDexFile, idx, pCode, codeOffset);
    }

    // Try-catch blocks.
    dumpCatches(pDexFile, pCode);

    // Positions and locals table in the debug info.
    bool is_static = (flags & kAccStatic) != 0;
    fprintf(gOutFile, "      positions     : \n");
    pDexFile->DecodeDebugPositionInfo(accessor.DebugInfoOffset(), dumpPositionsCb, nullptr);
    fprintf(gOutFile, "      locals        : \n");
    accessor.DecodeDebugLocalInfo(is_static, idx, dumpLocalsCb, nullptr);
}


/*
 * Dumps a method.
 */
static void dumpMethod(const DexFile *pDexFile, u4 idx, u4 flags,
                       const DexFile::CodeItem *pCode, u4 codeOffset, int i) {
//    // Bail for anything private if export only requested.
//    if (gOptions.exportsOnly && (flags & (kAccPublic | kAccProtected)) == 0) {
//        return;
//    }

    const DexFile::MethodId &pMethodId = pDexFile->GetMethodId(idx);
    const char *name = pDexFile->StringDataByIdx(pMethodId.name_idx_);
    const Signature signature = pDexFile->GetMethodSignature(pMethodId);
    char *typeDescriptor = strdup(signature.ToString().c_str());
    const char *backDescriptor = pDexFile->StringByTypeIdx(pMethodId.class_idx_);
    char *accessStr = createAccessFlagStr(flags, kAccessForMethod);

    if (true) {
        fprintf(gOutFile, "    #%d              : (in %s)\n", i, backDescriptor);
        fprintf(gOutFile, "      name          : '%s'\n", name);
        fprintf(gOutFile, "      type          : '%s'\n", typeDescriptor);
        fprintf(gOutFile, "      access        : 0x%04x (%s)\n", flags, accessStr);
        if (pCode == nullptr) {
            fprintf(gOutFile, "      code          : (none)\n");
        } else {
            fprintf(gOutFile, "      code          -\n");
            dumpCode(pDexFile, idx, flags, pCode, codeOffset);
        }
        if (true) {
            fputc('\n', gOutFile);
        }
    } else {

    }

    bail:
    free(typeDescriptor);
    free(accessStr);
}

/*
 * Dumps an interface that a class declares to implement.
 */
static void dumpInterface(const DexFile *pDexFile, const DexFile::TypeItem &pTypeItem, int i) {
    const char *interfaceName = pDexFile->StringByTypeIdx(pTypeItem.type_idx_);
    if (true) {
        fprintf(gOutFile, "    #%d              : '%s'\n", i, interfaceName);
    } else {
        std::unique_ptr<char[]> dot(descriptorToDot(interfaceName));
        fprintf(gOutFile, "<implements name=\"%s\">\n</implements>\n", dot.get());
    }
}


/*
 * Dumps an instance field.
 */
static void dumpIField(const DexFile *pDexFile, u4 idx, u4 flags, int i) {
    dumpSField(pDexFile, idx, flags, i, nullptr);
}


/*
 * Dumps the class.
 *
 * Note "idx" is a DexClassDef index, not a DexTypeId index.
 *
 * If "*pLastPackage" is nullptr or does not match the current class' package,
 * the value will be replaced with a newly-allocated string.
 */
static void dumpClass(const DexFile *pDexFile, int idx, char **pLastPackage) {
    const DexFile::ClassDef &pClassDef = pDexFile->GetClassDef(idx);
//
//    // Omitting non-public class.
//    if (gOptions.exportsOnly && (pClassDef.access_flags_ & kAccPublic) == 0) {
//        return;
//    }

//    if (gOptions.showSectionHeaders) {
//        dumpClassDef(pDexFile, idx);
//    }

//    if (gOptions.showAnnotations) {
//        dumpClassAnnotations(pDexFile, idx);
//    }

//    if (gOptions.showCfg) {
//        dumpCfg(pDexFile, idx);
//        return;
//    }

    // For the XML output, show the package name.  Ideally we'd gather
    // up the classes, sort them, and dump them alphabetically so the
    // package name wouldn't jump around, but that's not a great plan
    // for something that needs to run on the device.
    const char *classDescriptor = pDexFile->StringByTypeIdx(pClassDef.class_idx_);
    if (!(classDescriptor[0] == 'L' &&
          classDescriptor[strlen(classDescriptor) - 1] == ';')) {
        // Arrays and primitives should not be defined explicitly. Keep going?
        LOG(WARNING) << "Malformed class name '" << classDescriptor << "'";
    }

    // General class information.
    char *accessStr = createAccessFlagStr(pClassDef.access_flags_, kAccessForClass);
    const char *superclassDescriptor;
    if (!pClassDef.superclass_idx_.IsValid()) {
        superclassDescriptor = nullptr;
    } else {
        superclassDescriptor = pDexFile->StringByTypeIdx(pClassDef.superclass_idx_);
    }
    if (true) {
        fprintf(gOutFile, "Class #%d            -\n", idx);
        fprintf(gOutFile, "  Class descriptor  : '%s'\n", classDescriptor);
        fprintf(gOutFile, "  Access flags      : 0x%04x (%s)\n", pClassDef.access_flags_,
                accessStr);
        if (superclassDescriptor != nullptr) {
            fprintf(gOutFile, "  Superclass        : '%s'\n", superclassDescriptor);
        }
        fprintf(gOutFile, "  Interfaces        -\n");
    } else {

    }

    // Interfaces.
    const DexFile::TypeList *pInterfaces = pDexFile->GetInterfacesList(pClassDef);
    if (pInterfaces != nullptr) {
        for (u4 i = 0; i < pInterfaces->Size(); i++) {
            dumpInterface(pDexFile, pInterfaces->GetTypeItem(i), i);
        }  // for
    }

    // Fields and methods.
    const u1 *pEncodedData = pDexFile->GetClassData(pClassDef);
    if (pEncodedData == nullptr) {
        if (true) {
            fprintf(gOutFile, "  Static fields     -\n");
            fprintf(gOutFile, "  Instance fields   -\n");
            fprintf(gOutFile, "  Direct methods    -\n");
            fprintf(gOutFile, "  Virtual methods   -\n");
        }
    } else {
        ClassDataItemIterator pClassData(*pDexFile, pEncodedData);

        // Prepare data for static fields.
        const u1 *sData = pDexFile->GetEncodedStaticFieldValuesArray(pClassDef);
        const u4 sSize = sData != nullptr ? DecodeUnsignedLeb128(&sData) : 0;

        // Static fields.
        if (true) {
            fprintf(gOutFile, "  Static fields     -\n");
        }
        for (u4 i = 0; pClassData.HasNextStaticField(); i++, pClassData.Next()) {
            dumpSField(pDexFile,
                       pClassData.GetMemberIndex(),
                       pClassData.GetRawMemberAccessFlags(),
                       i,
                       i < sSize ? &sData : nullptr);
        }  // for

        // Instance fields.
        if (true) {
            fprintf(gOutFile, "  Instance fields   -\n");
        }
        for (u4 i = 0; pClassData.HasNextInstanceField(); i++, pClassData.Next()) {
            dumpIField(pDexFile,
                       pClassData.GetMemberIndex(),
                       pClassData.GetRawMemberAccessFlags(),
                       i);
        }  // for

        // Direct methods.
        if (true) {
            fprintf(gOutFile, "  Direct methods    -\n");
        }
        for (int i = 0; pClassData.HasNextDirectMethod(); i++, pClassData.Next()) {
            dumpMethod(pDexFile, pClassData.GetMemberIndex(),
                       pClassData.GetRawMemberAccessFlags(),
                       pClassData.GetMethodCodeItem(),
                       pClassData.GetMethodCodeItemOffset(), i);
        }  // for

        // Virtual methods.
        if (true) {
            fprintf(gOutFile, "  Virtual methods   -\n");
        }
        for (int i = 0; pClassData.HasNextVirtualMethod(); i++, pClassData.Next()) {
            dumpMethod(pDexFile, pClassData.GetMemberIndex(),
                       pClassData.GetRawMemberAccessFlags(),
                       pClassData.GetMethodCodeItem(),
                       pClassData.GetMethodCodeItemOffset(), i);
        }  // for
    }

    // End of class.
    if (true) {
        const char *fileName;
        if (pClassDef.source_file_idx_.IsValid()) {
            fileName = pDexFile->StringDataByIdx(pClassDef.source_file_idx_);
        } else {
            fileName = "unknown";
        }
        fprintf(gOutFile, "  source_file_idx   : %d (%s)\n\n",
                pClassDef.source_file_idx_.index_, fileName);
    } else if (false) {
        fprintf(gOutFile, "</class>\n");
    }

    free(accessStr);
}


static void processDexFile(const char *fileName,
                           const DexFile *pDexFile, size_t i, size_t n) {
//    if (true) {
//        fputs("Opened '", gOutFile);
//        fputs(fileName, gOutFile);
//        if (n > 1) {
//            fprintf(gOutFile, ":%s", DexFileLoader::GetMultiDexClassesDexName(i).c_str());
//        }
//        fprintf(gOutFile, "', DEX version '%.3s'\n", pDexFile->GetHeader().magic_ + 4);
//    }

    // Headers.
    if (true) {
        dumpFileHeader(pDexFile);
    }

    // Iterate over all classes.
    char *package = nullptr;
    const uint32_t classDefsSize = pDexFile->GetHeader().class_defs_size_;
    for (uint32_t i = 0; i < classDefsSize; i++) {
        dumpClass(pDexFile, i, &package);
    }  // for
//
//    // Iterate over all method handles.
//    for (u4 i = 0; i < pDexFile->NumMethodHandles(); ++i) {
//        dumpMethodHandle(pDexFile, i);
//    }  // for
//
//    // Iterate over all call site ids.
//    for (u4 i = 0; i < pDexFile->NumCallSiteIds(); ++i) {
//        dumpCallSite(pDexFile, i);
//    }  // for
//
//    // Free the last package allocated.
//    if (package != nullptr) {
//        fprintf(gOutFile, "</package>\n");
//        free(package);
//    }
//
//    // Close XML context.
//    if (gOptions.outputFormat == OUTPUT_XML) {
//        fprintf(gOutFile, "</api>\n");
//    }
}


const DexFile::MethodId *
findMethod(const char *methodName, const char *methodSignatureToFind, const DexFile *pDexFile,
           uint32_t idx) {
    const DexFile::MethodId &pMethodId = pDexFile->GetMethodId(idx);
    const char *name = pDexFile->StringDataByIdx(pMethodId.name_idx_);
    const Signature signature = pDexFile->GetMethodSignature(pMethodId);
    const char *methodSignature = signature.ToString().c_str();

    if (strcmp(name, methodName) == 0 &&
        strcmp(methodSignature, methodSignatureToFind) == 0) {
        return &pMethodId;
    }
    return nullptr;
}

bool findMethodInfo(const char *methodName, const char *methodSignatureToFind,
                    const DexFile *pDexFile,
                    const ClassDataItemIterator &pClassData,
                    const DexFile::MethodId *&pFoundMethodId,
                    uint32_t &methodMemberIndex, uint32_t &methodAccessFlags,
                    const DexFile::CodeItem *&pMethodCodeItem,
                    uint32_t &methodCodeItemOffset) {
    const DexFile::MethodId *pMethodId = findMethod(methodName, methodSignatureToFind,
                                                    pDexFile,
                                                    pClassData.GetMemberIndex());
    if (nullptr != pMethodId) {
        pFoundMethodId = pMethodId;
        methodMemberIndex = pClassData.GetMemberIndex();
        methodAccessFlags = pClassData.GetRawMemberAccessFlags();
        pMethodCodeItem = pClassData.GetMethodCodeItem();
        methodCodeItemOffset = pClassData.GetMethodCodeItemOffset();
        return true;
    }
    return false;
}

static bool initialized = false;

struct MyClassDef {
    const DexFile::ClassDef *pClassDef;
    const DexFile *pDexFile;
};

// static std::unordered_map<std::string, std::unique_ptr<const MyClassDef>> class_def_map;
static std::unordered_map<std::string, MyClassDef> class_def_map;

static std::vector<std::unique_ptr<const art_lkchan::DexFile>> dex_files;

extern "C" JNIEXPORT jstring JNICALL
Java_com_lkchan_dexfile_MainActivity_getApkDexInfo(
        JNIEnv *env,
        jobject /* this */, jstring path) {
    char r[100];
    sprintf(r, " dex file count = %d dex version=%d", dex_files.size(),
            !dex_files.empty() ? dex_files[0].get()->GetDexVersion() : 0);
    return env->NewStringUTF(r);
}

extern "C" JNIEXPORT jobject JNICALL
Java_com_lkchan_dexfile_MainActivity_invoke(JNIEnv *env, jobject thiz, jstring class_name,
                                            jstring method_name, jstring method_signature) {
    if (!initialized) {
        return env->NewStringUTF("not initialized!");
    }
    const char *className = env->GetStringUTFChars(class_name, nullptr);
    const char *methodName = env->GetStringUTFChars(method_name, nullptr);
    const char *methodSignature = env->GetStringUTFChars(method_signature, nullptr);

    MyClassDef myClassDef = class_def_map[std::string(className)];

    const DexFile *pDexFile = myClassDef.pDexFile;
    const DexFile::ClassDef *pClassDef = myClassDef.pClassDef;
    if (nullptr == pDexFile || nullptr == pClassDef) {
        return env->NewStringUTF("found class in cache but pDexFile or pClassDef is null");
    }

    const DexFile::MethodId *pMethodId = nullptr;
    uint32_t methodMemberIndex;
    uint32_t methodAccessFlags;
    const DexFile::CodeItem *pMethodCodeItem = nullptr;
    uint32_t methodCodeItemOffset;

    const u1 *pEncodedData = pDexFile->GetClassData(*pClassDef);
    if (pEncodedData != nullptr) {
        ClassDataItemIterator pClassData(*pDexFile, pEncodedData);
        while (pClassData.HasNextStaticField()) {
            pClassData.Next();
        }
        while (pClassData.HasNextInstanceField()) {
            pClassData.Next();
        }

        // Direct methods.
        for (int i = 0; pClassData.HasNextDirectMethod(); i++, pClassData.Next()) {
            bool found = findMethodInfo(methodName, methodSignature, pDexFile, pClassData,
                                        pMethodId,
                                        methodMemberIndex,
                                        methodAccessFlags, pMethodCodeItem,
                                        methodCodeItemOffset);
            if (found) {
                break;
            }
        }
        // Virtual methods.
        for (int i = 0; pClassData.HasNextVirtualMethod(); i++, pClassData.Next()) {
            bool found = findMethodInfo(methodName, methodSignature, pDexFile, pClassData,
                                        pMethodId,
                                        methodMemberIndex,
                                        methodAccessFlags, pMethodCodeItem,
                                        methodCodeItemOffset);
            if (found) {
                break;
            }
        }
    }

    if (nullptr == pMethodId || nullptr == pMethodCodeItem) {
        return env->NewStringUTF("method not found!");
    }

    CodeItemDebugInfoAccessor accessor(*pDexFile, pMethodCodeItem,
                                       methodMemberIndex);
    uint32_t idx = methodMemberIndex;
    const DexFile::CodeItem *pCode = pMethodCodeItem;
    uint32_t codeOffset = methodCodeItemOffset;
    uint32_t flags = methodAccessFlags;

    char buf[1024] = "METHOD: ";
    snappendf(buf, "%s->%s%s\n", className, methodName, methodSignature);
    snappendf(buf, "      registers     : %d\n", accessor.RegistersSize());
    snappendf(buf, "      ins           : %d\n", accessor.InsSize());
    snappendf(buf, "      outs          : %d\n", accessor.OutsSize());
    snappendf(buf, "      insns size    : %d 16-bit code units\n",
              accessor.InsnsSizeInCodeUnits());
    LOGD("%s", buf);
    dumpBytecodes(pDexFile, idx, pCode, codeOffset);
    dumpCatches(pDexFile, pCode);

    // Positions and locals table in the debug info.
    bool is_static = (flags & kAccStatic) != 0;
    fprintf(gOutFile, "      positions     : \n");
    pDexFile->DecodeDebugPositionInfo(accessor.DebugInfoOffset(), dumpPositionsCb, nullptr);
    fprintf(gOutFile, "      locals        : \n");
    accessor.DecodeDebugLocalInfo(is_static, idx, dumpLocalsCb, nullptr);

    return env->NewStringUTF(className);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_lkchan_dexfile_MainActivity_init(JNIEnv *env, jclass clazz, jstring path) {
    const char *fileName = env->GetStringUTFChars(path, nullptr);
    std::string error;
    const bool kVerifyChecksum = true;
    const bool kVerify = true;
    std::string content;
    if (!android_lkchan::base::ReadFileToString(fileName, &content)) {
        return;
    }
    const art_lkchan::DexFileLoader dex_file_loader = art_lkchan::DexFileLoader();
    std::string error_msg;
    if (!dex_file_loader.OpenAll(reinterpret_cast<const uint8_t *>(content.data()),
                                 content.size(),
                                 fileName,
                                 kVerify,
                                 kVerifyChecksum,
                                 &error_msg,
                                 &dex_files)) {
        return;
    }
    for (auto &dex_file : dex_files) {
        const DexFile *pDexFile = dex_file.get();
        const uint32_t classCount = pDexFile->GetHeader().class_defs_size_;
        if (classCount > kMaxClassCount) {
            exit(-1);
        }
        for (uint32_t i = 0; i < classCount; i++) {
            const DexFile::ClassDef &pClassDef = pDexFile->GetClassDef(static_cast<uint16_t>(i));
            const char *classDescriptor = pDexFile->StringByTypeIdx(pClassDef.class_idx_);
            MyClassDef myClassDef{.pClassDef = &pClassDef, .pDexFile=pDexFile};
            class_def_map[std::string(classDescriptor)] = myClassDef;
        }
    }
    initialized = true;
}